Skip to content

fix: local decoding of MQTT proxy messages in Virtual Node Server (#2358)#2363

Merged
Yeraze merged 3 commits intomainfrom
fix/mqtt-proxy-local-decode
Mar 21, 2026
Merged

fix: local decoding of MQTT proxy messages in Virtual Node Server (#2358)#2363
Yeraze merged 3 commits intomainfrom
fix/mqtt-proxy-local-decode

Conversation

@Yeraze
Copy link
Copy Markdown
Owner

@Yeraze Yeraze commented Mar 21, 2026

Summary

Fixes #2358 — MQTT proxy messages (mqttClientProxyMessage) sent to the Virtual Node Server are now locally decoded via the Server Channel Database, so traffic from channels not configured on the physical radio still appears in the UI.

Problem

When an MQTT proxy sends ToRadio.mqttClientProxyMessage to MeshMonitor via TCP, the VNS forwarded it directly to the physical radio without local processing. If the radio didn't have the channel configured, the message was silently dropped and never appeared in the UI.

Solution

  • Load mqtt.proto in the protobuf loader for ServiceEnvelope support
  • Add decodeServiceEnvelope() to meshtasticProtobufService — extracts MeshPacket from the ServiceEnvelope wrapper
  • Intercept mqttClientProxyMessage in handleClientMessage — decodes the envelope, sets viaMqtt=true, wraps in FromRadio, and feeds through processIncomingData() for server-side decryption
  • Still forwards to physical radio — the original ToRadio is always forwarded regardless of local processing result

Files Changed

File Change
src/server/protobufLoader.ts Load mqtt.proto for ServiceEnvelope support
src/server/meshtasticProtobufService.ts Add decodeServiceEnvelope() method
src/server/meshtasticProtobufService.test.ts 4 new tests for ServiceEnvelope decoding
src/server/virtualNodeServer.ts Add mqttClientProxyMessage handling block
src/server/virtualNodeServer.test.ts 4 new tests for MQTT proxy handling

Test plan

  • 3052 tests pass, 0 failures
  • Build clean (0 TypeScript errors)
  • ServiceEnvelope decode: valid envelope, invalid data, missing packet, empty data
  • VNS: packet marked viaMqtt, always forwarded to radio, graceful empty data handling

🤖 Generated with Claude Code

Yeraze and others added 3 commits March 21, 2026 18:02
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Load mqtt.proto for ServiceEnvelope support and add decode method
for extracting MeshPackets from MQTT proxy messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Node Server

Intercepts ToRadio.mqttClientProxyMessage, decodes the ServiceEnvelope,
extracts the MeshPacket, marks it viaMqtt=true, and feeds it through
processIncomingData for Server Channel Database decryption. The original
message is still forwarded to the physical radio.

Closes #2358

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
return null;
}

if (!data || data.length === 0) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding more specific error context here to aid debugging:

Suggested change
if (!data || data.length === 0) {
if (!data || data.length === 0) {
logger.debug('decodeServiceEnvelope: Empty or null data provided');
return null;
}

This will help distinguish between different failure modes when troubleshooting MQTT proxy issues.

gatewayId: decoded.gatewayId || undefined,
};
} catch (error) {
logger.warn('⚠️ Failed to decode ServiceEnvelope:', error);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider logging the error details to aid troubleshooting:

Suggested change
logger.warn('⚠️ Failed to decode ServiceEnvelope:', error);
logger.warn('⚠️ Failed to decode ServiceEnvelope:', (error as Error).message);

This provides more actionable debugging information for MQTT proxy issues while keeping logs clean.

// MQTT Proxy message: decode ServiceEnvelope locally for Server Channel Database decryption
// Then forward to physical radio as normal
const proxyMsg = toRadio.mqttClientProxyMessage;
const proxyData = proxyMsg.data;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding type safety and input validation:

Suggested change
const proxyData = proxyMsg.data;
const proxyData = proxyMsg?.data;
if (!proxyData || !(proxyData instanceof Uint8Array) || proxyData.length === 0) {
logger.warn(`Virtual node: MQTT proxy message from ${clientId} has invalid data payload`);
// Still forward to radio
logger.info(`Virtual node: Forwarding MQTT proxy message from ${clientId} to physical node`);
this.queueMessage(clientId, payload);
return;
}

This improves robustness by handling edge cases where proxyMsg.data might not be a Uint8Array as expected.

} else {
logger.warn(`Virtual node: MQTT proxy message from ${clientId} has no decodable packet, forwarding to radio only`);
}
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding more specific error context:

Suggested change
} catch (error) {
} catch (error) {
logger.error(`Virtual node: Failed to process MQTT proxy message locally from ${clientId}: ${(error as Error).message}`, error);
// Continue - still forward to physical node
}

This provides better debugging information while maintaining the robust error handling approach.


it('returns null for invalid data', () => {
if (!protobufInitialized) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great test case! Consider making the assertion more explicit about what constitutes valid vs invalid results:

Suggested change
const result = service.decodeServiceEnvelope(new Uint8Array([0xFF, 0xFF, 0xFF]));
// Should return null for invalid data that cannot be decoded as ServiceEnvelope
expect(result).toBeNull();

This makes the test intent clearer and ensures we're specifically testing the invalid data path.

});
});

describe('Virtual Node Server - MQTT Proxy Message Handling', () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are well-designed for documenting the expected behavior! Consider adding a test that verifies the actual flow integration:

Suggested change
describe('Virtual Node Server - MQTT Proxy Message Handling', () => {
describe('Virtual Node Server - MQTT Proxy Message Handling', () => {
// ... existing tests ...
it('should process MQTT proxy messages through the complete flow', async () => {
// Test that demonstrates the full: ToRadio → ServiceEnvelope decode → FromRadio → processIncomingData flow
// This would help catch integration issues between the components
});
});

This would verify the end-to-end integration between the VNS and protobuf service.

@Yeraze Yeraze merged commit 3c3c0bf into main Mar 21, 2026
17 checks passed
@Yeraze Yeraze deleted the fix/mqtt-proxy-local-decode branch March 21, 2026 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Local Channel Database fails to decode MQTT messages from mqtt-proxy

1 participant